import CryptoJS from 'crypto-js';

export default class sign {
    // 密钥
    static #secret = 'ych-YBox_0123456789'

    /**
     * 获取指定位数的随机数
     * @param num
     * @returns {number}
     */
    static getRandom(num) {
        return Math.floor((Math.random() + Math.floor(Math.random() * 9 + 1)) * Math.pow(10, num - 1))
    }

    /**
     * @param url 请求的url
     * @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
     */
    static parseQuery2Object(url) {
        let urlReg = /^[^\?]+\?([\w\W]+)$/,
            paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g,
            urlArray = urlReg.exec(url),
            result = {};
        if (urlArray && urlArray[1]) {
            let paramString = urlArray[1], paramResult;
            while ((paramResult = paramReg.exec(paramString)) != null) {
                result[decodeURIComponent(paramResult[1])] = decodeURIComponent(paramResult[2]);
            }
        }
        return result;
    }

    /**
     * @returns {*} 将两个对象合并成一个
     */
    static mergeObject(objectOne, objectTwo) {
        if (Object.keys(objectTwo).length > 0) {
            for (let key in objectTwo) {
                if (objectTwo.hasOwnProperty(key) === true) {
                    objectOne[key] = objectTwo[key];
                }
            }
        }
        return objectOne;
    }

    /**
     * 设置签名请求头
     * @param headers
     * @returns {*}
     */
    static setHeaders(data, type, headers) {
        if (typeof headers != 'object') {
            return
        }
        // 只有在请求存在Body且Body为非Form形式时才计算Content-MD5头
        if (data && (type && type != 'form')) {
            headers['content-md5'] = this.paramsSortToString(data, "&");
        } else {
            headers['content-md5'] = '';
        }
        headers['ca-version'] = 1
        headers['ca-date'] = new Date().toUTCString();
        headers['x-ca-key'] = this.#secret;
        headers['x-ca-timestamp'] = new Date().getTime();
        headers['x-ca-nonce'] = this.getRandom(10).toString();
        return headers
    }

    /**
     * 构造签名字符串
     * @param method  请求方法
     * @param path    请求路径
     * @param headers 请求头
     * @param data 请求参数
     * @returns {string}
     */
    static composeStringToSign(method, path, headers, data) {
        let contentMD5 = headers['content-md5'] || '';
        let contentType = headers['Content-Type'] || '';
        let date = headers['ca-date'] || '';
        let signHeaders = this.buildCanonicalHeaders(headers, 'x-ca-');

        let u = path.split('?')[0];
        let pathUnescaped = decodeURIComponent(u);
        let str = `${method.toLocaleLowerCase()}\n${contentMD5}\n${contentType}\n${date}\n${signHeaders}${pathUnescaped}`;

        // data不为空的时候才做拼接
        if (data != null && date != undefined && Object.keys(data).length > 0) {
            str += '\n' + this.paramsSortToString(data, "\n");
        }
        return str;
    }

    /**
     * 根据请求参数的key按ascii排序拼接字符串 格式: key=value\n
     * @param data 请求参数
     * @param split 分隔符
     * @returns {*}
     */
    static paramsSortToString(data, split) {
        if (!data) {
            return ''
        }
        let params = [];
        Object.keys(data).forEach(function (key) {
            let values = data[key];
            let type = typeof values;
            if (type === 'string' || type === 'number' || type === 'boolean') {
                params.push(`${key}=${values}`);
                return;
            }
            if (type === 'object' && values instanceof Array) {
                data[key].forEach(value => {
                    params.push(`${key}=${value}`);
                });
            }
        });
        params.sort();
        return params.join(split);
    }

    /**
     * 获取 请求头中以prefix开头的参数所拼接成的字符串
     * @param headers 请求头
     * @param prefix 'x-ca-'
     * @returns {string}
     */
    static buildCanonicalHeaders(headers, prefix) {
        let list = [];
        let keys = Object.keys(headers);

        let caHeaders = {};
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i];

            let lowerKey = key.toLowerCase().trim();
            if (lowerKey.startsWith(prefix)) {
                list.push(lowerKey);
                caHeaders[lowerKey] = headers[key];
            }
        }
        list.sort();

        let canonical = '';
        for (let _i = 0; _i < list.length; _i++) {
            let _key = list[_i];
            canonical += `${_key}:${caHeaders[_key]}\n`;
        }

        return canonical;
    }

    /**
     * 使用HMAC-SHA256和Base64计算签名的过程，其中Source参数为构造出的字符串。
     * @param source
     * @param secret
     * @returns {string}
     */
    static signString(source, secret) {
        // source 是按照网站或者自己的规则拼接的字符串，像 BI AN 里面就是将传递的参数拼接成字符串
        // key 是网站设置的秘钥，像 BI AN 就是secretKey
        // 此方法为HMAC-SHA256的签名方式
        let HMACSHA256str = CryptoJS.HmacSHA256(source, secret);
        // 使用16进制的方法加密，输出字符串
        return CryptoJS.enc.Base64.stringify(HMACSHA256str);
    }


    static signature(method, path, headers, queries) {
        let signature = this.signString(this.composeStringToSign(method, path, headers, queries), this.#secret)
        // Authorization字段介绍
        return "CA " + signature
    }

    static urlEncode(param, key, encode) {
        if (param == null) return '';
        let paramStr = '';
        let t = typeof (param);
        if (t === 'string' || t === 'number' || t === 'boolean') {
            paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param);
        } else {
            for (let i in param) {
                let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i);
                paramStr += this.urlEncode(param[i], k, encode);
            }
        }
        return paramStr;
    };
}
